﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Text;
using System.Linq;

using BizElements.TemplateEngine;

namespace BizElements.Generator
{
    public partial class TableListCtl : UserControl
    {
        IMetadataSource metaSource;
        Dictionary<string, object> selectedTablesInProjectSettings = new Dictionary<string, object>();
        IList<SqlObjectHeader> fetchedTablesAndViews;
        IDictionary<string, IDictionary<string, string>> allComponentVariables = new Dictionary<string, IDictionary<string, string>>();
        IEnumerable<ITemplate> allTemplates = new ITemplate[0];

        static readonly IClassItemNamingConvention DummyNamingConvention = new DefaultNamingConvention();

        public TableListCtl()
        {
            InitializeComponent();
        }

        #region Properties, events and getters.

        [Browsable(false)]
        public IMetadataSource MetaDataSource
        {
            get { return this.metaSource; }
            set { SetMetaSource(value); }
        }

        private void SetMetaSource(IMetadataSource value)
        {
            if (value == null)
            {
                this.uiTables.Items.Clear();
                this.metaSource = null;
            }
            else
            {
                bool hasChanged = (this.metaSource == null) || (this.metaSource.ConnectionString != value.ConnectionString);
                this.metaSource = value;
                if (hasChanged)
                    RefreshTableList();
            }
        }

        [Browsable(false)]
        public string[] SelectedTableAndViewNames
        {
            get { return GetSelectedTables(); }
        }

        private string[] GetSelectedTables()
        {
            List<string> selectedTables = new List<string>();
            foreach (ListViewItem table in this.uiTables.Items)
            {
                if (table.Checked)
                    selectedTables.Add(table.Text);
            }

            selectedTables.Sort();
            return selectedTables.ToArray();
        }

        [Browsable(false)]
        public Regex ExcludedColumnsRegex
        {
            get
            {
                string pattern = this.uiExcludedColumnsRegex.Text.Trim();
                if (string.IsNullOrEmpty(pattern))
                    return null;
                else
                    return new Regex(pattern);
            }
        }

        public string[] GetSelectedViewNames()
        {
            List<string> views = new List<string>();
            foreach (ListViewItem viewOrTable in this.uiTables.Items)
            {
                if (viewOrTable.Checked && viewOrTable.Tag is SqlObjectType)
                {
                    if ((SqlObjectType)viewOrTable.Tag == SqlObjectType.View)
                        views.Add(viewOrTable.Text);
                }
            }

            return views.ToArray();
        }

        public IDictionary<string, IDictionary<string, string>> GetAllComponentVariables()
        {
            return this.allComponentVariables;
        }

        #endregion

        #region Refresh.

        private void uiRefresh_Click(object sender, EventArgs e)
        {
            string[] tablesAndViewNames = this.SelectedTableAndViewNames;
            this.selectedTablesInProjectSettings = ToDictionary(tablesAndViewNames);
            RefreshTableList();
        }

        private void RefreshTableList()
        {
            if (this.MetaDataSource == null)
                return;

            this.uiTablesPnl.Enabled = false;
            this.fetchedTablesAndViews = this.MetaDataSource.FetchTablesAndViews();
            if (this.TemplatesRequested != null)
                TemplatesRequested(this, new EventArgs());

            Rebind();
        }

        private void Rebind()
        {
            if (this.fetchedTablesAndViews == null)
                return;

            this.uiTables.BeginUpdate();
            this.uiTables.Items.Clear();
            foreach (SqlObjectHeader tbl in this.fetchedTablesAndViews)
            {
                ListViewItem item = this.uiTables.Items.Add(tbl.Name);
                item.SubItems.Add(tbl.TypeName);
                tbl.Metadata = "";
                foreach (Pair<string, string> nameAndVal in GetComponentVariables(tbl.Name))
                {
                    if (!string.IsNullOrEmpty(nameAndVal.Value))
                        tbl.Metadata += string.Format("{0}={1};", nameAndVal.Name, nameAndVal.Value);
                }

                item.SubItems.Add(tbl.Metadata);
                item.Tag = tbl.Type;
                item.Checked = this.selectedTablesInProjectSettings.ContainsKey(tbl.Name);
            }

            this.uiTables.EndUpdate();
            this.uiTablesPnl.Enabled = true;
        }

        #endregion

        #region Settings.

        public void ReadSettings(BizElementsSettings settings)
        {
            this.uiExcludedColumnsRegex.Text = settings["TableList_ExcludedColumnsRegex"];
            string delimitedTables = settings["TableList_SelectedTables"] ?? "";
            string[] tablesAndViewNames = delimitedTables.Split(';');
            Array.Sort<string>(tablesAndViewNames);
            this.selectedTablesInProjectSettings = ToDictionary(tablesAndViewNames);
            this.allComponentVariables = Triple<string, string, string>.ParseDictionary(settings["TableList_ComponentVariables"], ';', '/', '=');
            RefreshTableList();
        }

        public void WriteSettings(BizElementsSettings settings)
        {
            settings["TableList_ExcludedColumnsRegex"] = this.uiExcludedColumnsRegex.Text.Trim();
            string[] tablesAndViewNames = this.SelectedTableAndViewNames;
            Array.Sort<string>(tablesAndViewNames);
            settings["TableList_SelectedTables"] = string.Join(";", tablesAndViewNames);
            this.selectedTablesInProjectSettings = ToDictionary(tablesAndViewNames);
            
            StringBuilder concatenatedComponentVaribles = new StringBuilder();
            foreach (KeyValuePair<string, IDictionary<string, string>> componentNameAndVariables in this.allComponentVariables)
            {
                string componentName = componentNameAndVariables.Key;
                foreach (KeyValuePair<string, string> varNameAndValue in componentNameAndVariables.Value)
                {
                    string varName = varNameAndValue.Key;
                    string varValue = varNameAndValue.Value;
                    if (!string.IsNullOrEmpty(componentName) && !string.IsNullOrEmpty(varName) && !string.IsNullOrEmpty(varValue))
                        concatenatedComponentVaribles.AppendFormat("{0}/{1}={2};", componentName, varName, varValue);
                }
            }
            settings["TableList_ComponentVariables"] = concatenatedComponentVaribles.ToString();
        }

        private static Dictionary<string, object> ToDictionary(IEnumerable<string> tablesAndViewNames)
        {            
            Dictionary<string, object> indexedList = new Dictionary<string, object>();
            foreach (string tbl in tablesAndViewNames)
                indexedList[tbl] = null;

            return indexedList;
        }

        #endregion

        #region Component variables.

        private void uiTables_DoubleClick(object sender, EventArgs e)
        {
            var focusedItem = this.uiTables.FocusedItem;
            if (focusedItem == null)
                return;

            // Slow double click may disable item.
            focusedItem.Checked = true;
            int idx = focusedItem.Index;
            if ((idx >= 0) && (idx < this.fetchedTablesAndViews.Count))
            {
                ComponentDetailsFrm varEditor = new ComponentDetailsFrm();
                string componentName = this.fetchedTablesAndViews[idx].Name;
                ITypeMapper typeMapper = DbGeneratorComponentFactory.GetTypeMappers(this.metaSource.DBMS).First();
                Table table = this.metaSource.FetchTableMetadata(componentName, new string[] { componentName }, DummyNamingConvention, typeMapper);
                table.GeneratedTables = new Table[] { table };
                ITemplateInputComponentBuilder componentBuilder = new BizElements.Generator.OrmComponents.DbComponentBuilder();
                if (componentBuilder.IsCompatibleItem(table))
                {
                    varEditor.SetComponentData(componentBuilder.Build(table),
                        new ComponentType[] { BizElements.Generator.OrmComponents.DbComponentType.Field },
                        this.allTemplates,
                        this.allComponentVariables);
                }

                varEditor.VariablesChanged += new EventHandler<EventArgs<IEnumerable<Triple<string, string, string>>>>(varEditor_VariablesChanged);
                varEditor.ShowDialog();
            }
        }

        void varEditor_VariablesChanged(object sender, EventArgs<IEnumerable<Triple<string, string, string>>> e)
        {
            foreach (Triple<string, string, string> componentVariableAndValue in e.Item)
                SetComponentVariableValue(componentVariableAndValue.Key1, componentVariableAndValue.Key2, componentVariableAndValue.Value);

            Rebind();
        }

        private void SetComponentVariableValue(string componentName, string variableName, string variableValue)
        {
            if (string.IsNullOrEmpty(componentName) || string.IsNullOrEmpty(variableName))
                return;

            IDictionary<string, string> componentVariables;
            this.allComponentVariables.TryGetValue(componentName, out componentVariables);
            if (componentVariables == null)
            {
                componentVariables = new Dictionary<string, string>();
                this.allComponentVariables[componentName] = componentVariables;
            }

            componentVariables[variableName] = variableValue;
        }

        private List<Pair<string, string>> GetComponentVariables(string componentName)
        {
            List<Pair<string, string>> currentValues = new List<Pair<string, string>>();
            IDictionary<string, string> componentVariables;
            this.allComponentVariables.TryGetValue(componentName, out componentVariables);
            if (componentVariables == null)
                return currentValues;

            foreach (KeyValuePair<string, string> nameAndValue in componentVariables)
                currentValues.Add(new Pair<string, string>(nameAndValue.Key, nameAndValue.Value));

            return currentValues;
        }

        #endregion

        // When this event occurs, parent control should provide all templates via SetTemplate() method.
        public event EventHandler TemplatesRequested;

        public void SetTemplates(IEnumerable<ITemplate> allTemplates)
        {
            if (allTemplates == null)
                this.allTemplates = new ITemplate[0];
            else
                this.allTemplates = allTemplates;
        }
    }
}
